/*
 * File     : DniInternal.java
 * Created  : 23 Apr 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.internal;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.googlecode.dni.Allocator;
import com.googlecode.dni.DirectNativeInterface;
import com.googlecode.dni.IllegalLibraryException;
import com.googlecode.dni.InitialisationOption;
import com.googlecode.dni.Library;
import com.googlecode.dni.internal.MemoryAccess.MemoryAccessor;
import com.googlecode.dni.internal.library.LibraryBuilder;
import com.googlecode.dni.internal.platform.Platform;
import com.googlecode.dni.internal.platform.PlatformUtilities;
import com.googlecode.dni.internal.structure.RecursiveStructureException;
import com.googlecode.dni.internal.structure.StructureBuilder;
import com.googlecode.dni.internal.structure.StructureRecord;
import com.googlecode.dni.type.LifetimeManagedList;
import com.googlecode.dni.type.NativeObjectList;
import com.googlecode.dni.type.Pointer;
import com.googlecode.dni.type.structure.Structure;


/**
 * <p>
 *  Provides the central point of access for all the internal services.
 * </p>
 * <p>
 *  The intention is that all of the classes in the API section come through
 *  this gateway.
 * </p>
 *
 * @author Matthew Wilson
 */
public final class DniInternal
{

    /** Whether debug mode is enabled. */
    public static final boolean DEBUG_MODE;

    /** The platform. */
    public static final Platform PLATFORM;

    /** The allocator. */
    public static final Allocator ALLOCATOR;

    /** The accessor. */
    public static final MemoryAccessor MEMORY_ACCESSOR;

    /** Logger for DNI. */
    public static final Logger LOGGER = Logger.getLogger( "DNI" );

    private static final Map< Class< ? >, Object > LIBRARIES =
        new WeakHashMap< Class< ? >, Object >();

    private static final Map< Class< ? >, StructureRecord > STRUCTURES =
        new WeakHashMap< Class< ? >, StructureRecord >();

    private static final boolean DUMP_BYTE_CODE;

    private static final File DUMP_BYTE_CODE_DIR;

    /** Share a lock because libraries can depend on structures. */
    private static final Object LOCK = new Object();

    static
    {
        DEBUG_MODE = (Boolean) DirectNativeInterface.getOption( InitialisationOption.DEBUG_MODE );
        PLATFORM = PlatformUtilities.getPlatform();
        ALLOCATOR = MemoryAccess.ALLOCATOR;
        MEMORY_ACCESSOR = MemoryAccess.MEMORY_ACCESSOR;

        String dir = (String) DirectNativeInterface.getOption( InitialisationOption.DUMP_BYTE_CODE );
        if ( dir == null || dir.isEmpty() )
        {
            DUMP_BYTE_CODE = false;
            DUMP_BYTE_CODE_DIR = null;
        }
        else
        {
            DUMP_BYTE_CODE = true;
            DUMP_BYTE_CODE_DIR = new File( dir );
            DUMP_BYTE_CODE_DIR.mkdirs();
        }
    }

    /** Prevents instantiation. */
    private DniInternal()
    {
        throw new UnsupportedOperationException();
    }

    /**
     * <p>
     *  Creates an instance of a library API, given its type.
     * </p>
     * <p>
     *  The native library specified by the {@link Library} annotation is first
     *  loaded, then an implementation of the given interface is created that
     *  will call the method's equivalent native function.
     * </p>
     * <p>
     *  See the class documentation for details of how interface methods require
     *  annotation to specify the native function's details.
     * </p>
     *
     * @param <T>
     *            the type of interface
     * @param type
     *            the type of interface
     *
     * @return an implementation of the given interface
     *
     * @throws LinkageError
     *             if the library or libraries could not be loaded
     * @throws IllegalLibraryException
     *             if the given type is not an interface, or is not correctly
     *             annotated
     */
    public static < T > T getLibrary( final Class< T > type ) throws LinkageError
    {
        synchronized ( LOCK )
        {
            T library = type.cast( LIBRARIES.get( type ) );
            if ( library != null )
            {
                return library;
            }

            LibraryBuilder< T > builder = LibraryBuilder.create( type );
            library = builder.createLibrary();

            LIBRARIES.put( type, library );

            return library;
        }
    }

    /**
     * <p>
     *  Obtains the structure record for a structure type.
     * </p>
     *
     * @param type
     *            the type of structure
     *
     * @return a record for the structure
     */
    public static StructureRecord getStructureRecord( final Class< ? extends Structure > type )
    {
        synchronized ( LOCK )
        {
            StructureRecord record = STRUCTURES.get( type );
            if ( record == StructureRecord.IN_PROGRESS )
            {
                throw new RecursiveStructureException( "Recursive call to create: " + type );
            }
            if ( record != null )
            {
                return record;
            }

            // TODO This assumes that there aren't already classes called by these names...
            String implName = getStructureImplName( type );
            String factoryImplName = getStructureFactoryImplName( type );

            // don't permit recursive calls
            STRUCTURES.put( type, StructureRecord.IN_PROGRESS );

            StructureBuilder< ? > builder = StructureBuilder.create( type, implName, factoryImplName );

            record = builder.build();
            STRUCTURES.put( type, record );

            builder.postCreate();

            return record;
        }
    }

    /**
     * <p>
     *  Determines the name of the structure implementation type.
     * </p>
     *
     * @param type
     *            the structure interface type
     * @return the implementation type name (byte-code style)
     */
    public static String getStructureImplName( final Class< ? extends Structure > type )
    {
        return type.getName().replace( '.', '/' ) + "_Impl_";
    }

    /**
     * <p>
     *  Determines the name of the structure factory implementation type.
     * </p>
     *
     * @param type
     *            the structure interface type
     * @return the implementation factory type name (byte-code style)
     */
    public static String getStructureFactoryImplName( final Class< ? extends Structure > type )
    {
        return type.getName().replace( '.', '/' ) + "_FactoryImpl_";
    }

    /**
     * Defines a class.
     *
     * @param name
     *            the name (byte-code format)
     * @param classLoader
     *            the class loader
     * @param classFile
     *            the class file
     *
     * @return the loaded class
     */
    public static Class< ? > defineClass( final String name, final ClassLoader classLoader, final byte[] classFile )
    {
        if ( DUMP_BYTE_CODE )
        {
            LOGGER.fine( "Dumping class file for " + name );

            File file = new File( DUMP_BYTE_CODE_DIR, name.replace( '.', File.separatorChar ) + ".class" );
            file.getParentFile().mkdirs();

            try
            {
                new FileOutputStream( file ).write( classFile );
            }
            catch ( IOException exception )
            {
                LOGGER.log( Level.WARNING, "Failed to dump class for " + name, exception );
            }
        }

        return NativeUnsafeAccess.defineClass( name, classLoader, classFile, classFile.length );
    }

    /**
     * Creates a pointer list.
     *
     * @param pointer
     *            the base pointer
     * @param size
     *            the number of elements
     * @param autoFree
     *            whether to free the memory on finalize
     * @return a new instance
     */
    public static NativeObjectList< Pointer > createPointerList( final Pointer pointer,
                                                                 final int size,
                                                                 final boolean autoFree )
    {
        return new PointerList( pointer.getAddress(), size, autoFree );
    }

    /**
     * Creates a pointer list.
     *
     * @param pointer
     *            the base pointer
     * @param size
     *            the number of elements
     * @return a new instance
     */
    public static LifetimeManagedList< Pointer > createPointerLifetimeManagedList( final Pointer pointer,
                                                                                   final int size )
    {
        return new PointerLifetimeManagedList( pointer.getAddress(), size );
    }

}
